﻿using SearchForWorkitemsChanges.ExternalActions;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;

namespace SearchForWorkitemsChanges
{
    /// <summary>
    /// Main Window ViewModel. Implement <see cref="VmBase{T}"/>
    /// </summary>
    internal class VmMainWindow : VmBase<VmMainWindow>
    {
        private readonly ITfsInfoProvider currentTfsProvider;

        /// <summary>
        /// Initializes a new instance of the <see cref="VmMainWindow" /> class.
        /// </summary>
        public VmMainWindow()
        {
            this.currentTfsProvider = new TeamExplorerCacheInfoProvider();

            #region currentTfsProviderPropertyChanged rerouting Event

            this.currentTfsProvider.PropertyChanged +=
                (s, e) =>
                {
                    switch (e.PropertyName)
                    {
                        case "CurrentProject":
                            this.NotifyPropertyChanged(p => p.ProjectName); // Reroute event...
                            //this.ResetFolders(); // Done on Event PropertyChange of current instance > ProjectName
                            //this.ResetChangesets(); // Done on Event PropertyChange of current instance > ProjectName
                            //this.ResetArtifacts(); // Done on Event PropertyChange of current instance > ProjectName
                            break;
                        case "CurrentCollection":
                            this.NotifyPropertyChanged(p => p.ProjectCollectionName); // Reroute event...
                            this.NotifyPropertyChanged(p => p.ProjectCollectionUri); // Reroute event...
                            break;
                        default:
                            break;
                    }
                };

            #endregion

            #region Define CmdChangeSource

            this.CmdChangeSource = new RelayCommand(
                (o) => !this.isFoldersLoading
                , (o) =>
                {
                    this.currentTfsProvider.ChooseNewTeamProjectCollection();
                    this.LoadFolders();
                    this.LoadWorkItemType();
                });

            #endregion Define CmdChangeSource

            #region Define CmdLoadChangeSets

            this.CmdLoadChangeSets = new RelayCommand(
                (o) => !this.isLoadingChangesets && this._dateFrom != DateTime.MinValue && this._dateTo != DateTime.MinValue && this.SelectedFolder != null
                , (o) => this.LoadChangesets());

            #endregion Define CmdLoadChangeSets

            #region Define CmdLoadArtifacts

            this.CmdLoadArtifacts = new RelayCommand(
                (o) => !this.isLoadingChangesets && this.ChangeSetFrom != null && this.ChangeSetTo != null && this.SelectedFolder != null
              , (o) => this.LoadArtifacts());
            #endregion Define CmdLoadArtifacts

            #region Define CmdExternalAction
            this.CmdExternalAction = new RelayCommand(
                  (p) => this._linkedArtifacts.Any()
                , (p) =>
                {
                    if (p != null)
                        if (p is IExternalAction)
                            ((IExternalAction)p).Execute(this._linkedArtifacts);
                });
            #endregion Define CmdExternalAction

            this.PropertyChanged += VmSelectSource_PropertyChanged;
        }

        /// <summary>
        /// Handles the PropertyChanged event of the VmMainWindow control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs" /> instance containing the event data.</param>
        private void VmSelectSource_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "ProjectName":
                    this.ResetFolders();
                    this.ResetChangesets();
                    break;
                case "DateFrom":
                case "DateTo":
                case "SelectedFolder":
                    this.ResetChangesets();
                    ((RelayCommand)this.CmdLoadChangeSets).NotifyCanExecuteChanged();
                    break;
                case "ChangeSetFrom":
                case "ChangeSetTo":
                    this.ResetArtifacts();
                    ((RelayCommand)this.CmdLoadArtifacts).NotifyCanExecuteChanged();
                    ((RelayCommand)this.CmdExternalAction).NotifyCanExecuteChanged();
                    break;
            }


        }

        #region Linked Artifacts

        private void ResetArtifacts()
        {
            App.Current.Dispatcher.Invoke(() => this._linkedArtifacts.Clear());
        }

        private bool isArtifactsLoading = false;
        private void NotifyIsArtifactsLoading(bool value)
        {
            this.isArtifactsLoading = value;
            ((RelayCommand)this.CmdLoadArtifacts).NotifyCanExecuteChanged();
        }

        private void LoadArtifacts()
        {
            this.NotifyIsArtifactsLoading(true);
            var th = new Thread(
                () =>
                {
                    var idFrom = this.ChangeSetFrom.ChangesetId;
                    var idTo = this.ChangeSetTo.ChangesetId;
                    var changesets = this._changeSets.Where(c => c.ChangesetId >= idFrom && c.ChangesetId <= idTo).ToList();
                    var workitems = changesets.Where(c => c.WorkItems.Any()).Select(x => x.WorkItems); // Lauch the tfs request for finding all artifacts and get workitems from artifacts

                    App.Current.Dispatcher.Invoke(() => this._linkedArtifacts.Clear());

                    foreach (var wis in workitems)
                        foreach (var wi in wis)
                            if (!this._linkedArtifacts.Any(x => x.Id == wi.Id))
                                App.Current.Dispatcher.Invoke(() => this._linkedArtifacts.Add(wi));

                    ((RelayCommand)this.CmdExternalAction).NotifyCanExecuteChanged();

                }) { IsBackground = true };
            th.Start();
        }

        private readonly ObservableCollection<WorkItem> _linkedArtifacts = new ObservableCollection<WorkItem>();

        public IEnumerable LinkedArtifacts { get { return this._linkedArtifacts; } }

        #endregion Linked Artifacts

        #region WorkItemsType

        private readonly ObservableCollection<SelectableItem<string>> _workitemTypes = new ObservableCollection<SelectableItem<string>>();

        private void LoadWorkItemType()
        {
            App.Current.Dispatcher.Invoke(() => this._workitemTypes.Clear());

            if (this.currentTfsProvider.CurrentProject != null)
            {
                foreach (var item in this.currentTfsProvider.GetWorkItemTypes())
                {
                    var selectableItem = new SelectableItem<string>(item);
                    App.Current.Dispatcher.Invoke(() => this._workitemTypes.Add(selectableItem));
                }
            }
            this.NotifyPropertyChanged(p => p.WorkItemTypes); // For allowing to enable / disable itemscontrol if no itemssource
        }

        public IEnumerable<SelectableItem<string>> WorkItemTypes
        {
            get { return this._workitemTypes; }
        }

        #endregion WorkItemsType

        #region ChangeSets

        private void ResetChangesets()
        {
            NotifyIsChangeSetLoading(true);
            App.Current.Dispatcher.Invoke(() => this._changeSets.Clear());
            NotifyIsChangeSetLoading(false);

            this.ChangeSetFrom = null;
            this.ChangeSetTo = null;
        }

        private bool isLoadingChangesets = false;
        private void NotifyIsChangeSetLoading(bool value)
        {
            this.isLoadingChangesets = value;
            ((RelayCommand)this.CmdLoadChangeSets).NotifyCanExecuteChanged();
            this.NotifyPropertyChanged(p => p.ChangeSets); // For allowing itemscontrol to be disabled on prop change
        }

        private void LoadChangesets()
        {
            this.NotifyIsChangeSetLoading(true);

            var th = new Thread(() =>
            {
                App.Current.Dispatcher.Invoke(() => this._changeSets.Clear());
                foreach (var item in this.currentTfsProvider.GetChangeSets(this.DateFrom, this.DateTo, this.SelectedFolder.MySelf.ServerItem))
                    App.Current.Dispatcher.Invoke(() => this._changeSets.Add(item));

                this.NotifyIsChangeSetLoading(false);

            }) { IsBackground = true };
            th.Start();
        }

        #endregion ChangeSets

        #region Folders

        private void ResetFolders()
        {
            App.Current.Dispatcher.Invoke(() => this._folders.Clear());
            this.SelectedFolder = null;
        }

        private bool isFoldersLoading = false;
        private void NotifyIsFoldersLoading(bool value)
        {
            this.isFoldersLoading = value;
            ((RelayCommand)this.CmdChangeSource).NotifyCanExecuteChanged();
        }

        /// <summary>
        /// Loads the Project base folders.
        /// </summary>
        private void LoadFolders()
        {
            if (this.currentTfsProvider.CurrentProject == null)
                this._folders.Clear();
            else
            {
                this.NotifyIsFoldersLoading(true);
                var th = new Thread(
                    () =>
                    {
                        App.Current.Dispatcher.Invoke(() => this._folders.Clear());

                        foreach (var item in this.currentTfsProvider.GetFolders())
                            App.Current.Dispatcher.Invoke(() => this._folders.Add(new FolderLazyLoader(this.currentTfsProvider, item)));

                        if (this._folders.Any())
                            this.SelectedFolder = this._folders.First();
                        this.NotifyIsFoldersLoading(false);
                    }) { IsBackground = true };
                th.Start();
            }
        }

        #endregion Folders



        /// <summary>
        /// Gets the <see cref="ICommand"/> for changing the TFS project source (TFS Collection \ project).
        /// </summary>
        public ICommand CmdChangeSource { get; private set; }

        /// <summary>
        /// Gets the <see cref="ICommand"/> for loading changesets linked to the current TFS Source between the <see cref="DateFrom"/> and <see cref="DateTo"/> <see cref="DateTime"/>.
        /// </summary>
        public ICommand CmdLoadChangeSets { get; private set; }

        /// <summary>
        /// Gets the <see cref="ICommand"/> for loading all artifacts between the <see cref="ChangeSetFrom"/> and <see cref="ChangeSetTo"/> <see cref="Changeset"/>.
        /// </summary>
        public ICommand CmdLoadArtifacts { get; private set; }

        /// <summary>
        /// Best WPF practice : use of ObservableCollection, never re instanciate, clear if empty needed.
        /// </summary>
        private readonly ObservableCollection<Changeset> _changeSets = new ObservableCollection<Changeset>();

        /// <summary>
        /// Gets an <see cref="IEnumerable"/> of <see cref="Changeset"/>. The returned value implements <see cref="System.Collections.Specialized.INotifyCollectionChanged"/>.
        /// </summary>
        public IEnumerable<Changeset> ChangeSets { get { return this._changeSets; } }

        /// <summary>
        /// Best WPF practice : use of ObservableCollection, never re instanciate, clear if empty needed.
        /// </summary>
        private readonly ObservableCollection<TfsItemLazyLoader> _folders = new ObservableCollection<TfsItemLazyLoader>();

        /// <summary>
        /// Gets an <see cref="IEnumerable"/> of <see cref="FolderLazyLoader"/>. The returned value implements <see cref="System.Collections.Specialized.INotifyCollectionChanged"/>.
        /// </summary>
        public IEnumerable<TfsItemLazyLoader> Folders { get { return this._folders; } }

        /// <summary>
        /// Gets the name of the project collection.
        /// </summary>
        public string ProjectCollectionName
        {
            get
            {
                return (this.currentTfsProvider.CurrentCollection == null || String.IsNullOrEmpty(this.currentTfsProvider.CurrentCollection.Name) ? "(none)" : this.currentTfsProvider.CurrentCollection.Name);
            }
        }

        /// <summary>
        /// Gets the project collection URI.
        /// </summary>
        public Uri ProjectCollectionUri
        {
            get
            {
                return (this.currentTfsProvider.CurrentCollection == null ? null : this.currentTfsProvider.CurrentCollection.Uri);
            }
        }

        /// <summary>
        /// Gets the name of the project.
        /// </summary>
        public string ProjectName
        {
            get
            {
                return (this.currentTfsProvider.CurrentCollection == null || String.IsNullOrEmpty(this.currentTfsProvider.CurrentProject.Name) ? "(none)" : this.currentTfsProvider.CurrentProject.Name);
            }
        }

        private DateTime _dateFrom;

        /// <summary>
        /// Gets or sets the <see cref="DateTime"/> starting the period.
        /// </summary>
        public DateTime DateFrom
        {
            get { return this._dateFrom; }
            set { this._dateFrom = value; this.NotifyPropertyChanged(p => p.DateFrom); }
        }

        private DateTime _dateTo;

        /// <summary>
        /// Gets or sets the <see cref="DateTime"/> ending the period.
        /// </summary>
        public DateTime DateTo
        {
            get { return this._dateTo; }
            set { this._dateTo = value; this.NotifyPropertyChanged(p => p.DateTo); }
        }

        private Changeset _changeSetFrom;

        /// <summary>
        /// Gets or sets the <see cref="Changeset"/> starting the period.
        /// </summary>
        public Changeset ChangeSetFrom
        {
            get { return this._changeSetFrom; }
            set { this._changeSetFrom = value; this.NotifyPropertyChanged(p => p.ChangeSetFrom); }
        }

        private Changeset _changeSetTo;

        /// <summary>
        /// Gets or sets the <see cref="Changeset"/> ending the period.
        /// </summary>
        public Changeset ChangeSetTo
        {
            get { return this._changeSetTo; }
            set { this._changeSetTo = value; this.NotifyPropertyChanged(p => p.ChangeSetTo); }
        }

        private TfsItemLazyLoader _selectedFolder;

        /// <summary>
        /// Gets or sets the <see cref="TfsItemLazyLoader"/> selected.
        /// </summary>
        public TfsItemLazyLoader SelectedFolder
        {
            get { return this._selectedFolder; }
            set { this._selectedFolder = value; this.NotifyPropertyChanged(p => p.SelectedFolder); }
        }

        /// <summary>
        /// Gets the external actions (addons).
        /// </summary>
        public IEnumerable<IExternalAction> ExternalActions { get { return ObjectResolver.ExternalActionsResolveAll(); } }

        /// <summary>
        /// Gets the <see cref="ICommand"/> for driving all External Actions Buttons.
        /// </summary>
        public ICommand CmdExternalAction { get; private set; }
    }
}
